/*
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package main;

import interfaces.CommandHandler;
import interfaces.TextHandler;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import utopiaintelpackage.BaseParser;
import botobjects.Command;

import commandhandlers.HelpHandler;
import commandhandlers.NewsHandler;
import commandhandlers.TextInputHandler;

/**
 * A class used for handling command settings and command handlers
 * 
 * 
 */
public enum CommandSettings {
   INSTANCE;

   private final Map<String, Command> handlers;
   private final Map<String, CommandHandler> instanciatedHandlers;
   private final Map<String, String> commandAliasMapping;

   private CommandSettings() {
	  handlers = new HashMap<String, Command>();
	  instanciatedHandlers = new HashMap<String, CommandHandler>();
	  commandAliasMapping = new HashMap<String, String>();

	  List<Command> commandList = Command.getCommands();

	  for (Command cmd : commandList) {
		 loadCommand(cmd);
	  }
   }

   /**
    * Returns the base command name for the given input. The input could for example be an alias of some command
    * 
    * @param cmd
    *           The command in question
    * @return a string with the name of the base command
    */
   public String getBaseCommand(final String cmd) {
	  return commandAliasMapping.containsKey(cmd.toLowerCase()) ? commandAliasMapping.get(cmd.toLowerCase()) : "";
   }

   /**
    * Returns a handler for the specified command
    * 
    * @param cmd
    * @return a CommandHandler for the specified command. Returns null if no handler is found
    */
   public CommandHandler getHandlerForCommand(final String cmd) {
	  Command cm = null;
	  if (handlers.containsKey(cmd.toLowerCase())) {
		 cm = handlers.get(cmd);
	  } else
		 return null;
	  final String className = cm.getClassName();
	  final String classURL = cm.getClassURL();
	  if ("".equals(className) || "".equals(classURL))
		 return null;

	  if (instanciatedHandlers.containsKey(className))
		 return instanciatedHandlers.get(className);
	  else
		 return instanciateHandler(className, classURL);
   }

   @SuppressWarnings("serial")
   public List<TextHandler> getTextHandlers() {
	  return new ArrayList<TextHandler>() {
		 {
			add(HelpHandler.getInstance());
			add(NewsHandler.getInstance());
			add(TextInputHandler.getInstance());
			add(BaseParser.INSTANCE);
		 }
	  };
   }

   /**
    * Instanciates a command handler and returns it
    * 
    * @param className
    *           The name of the class
    * @param classURL
    *           The URL of the class
    * @return An instance of a command handler. Returns null if the class cannot be instanciated
    */
   @SuppressWarnings({ "rawtypes", "unchecked" })
   public CommandHandler instanciateHandler(final String className, final String classURL) {
	  ClassLoader classLoader = this.getClass().getClassLoader();
	  try {
		 Class newHandlerClass = classLoader.loadClass(classURL);
		 Constructor[] c = newHandlerClass.getConstructors();
		 CommandHandler handler = null;
		 if (c.length == 0) {
			try {
			   Method method = newHandlerClass.getDeclaredMethod("newInstance", new Class[] {});
			   handler = (CommandHandler) method.invoke(null);
			} catch (SecurityException e) {
			   e.printStackTrace();
			} catch (NoSuchMethodException e) {
			   e.printStackTrace();
			}
		 } else {
			for (final Constructor con : c) {
			   Class[] classes = con.getParameterTypes();
			   if (classes.length == 0) {
				  handler = (CommandHandler) con.newInstance();
			   }
			}
		 }
		 instanciatedHandlers.put(className, handler);
		 return handler;
	  } catch (final ClassNotFoundException e) {
		 Logger.getLogger("Error").error("", e);
	  } catch (final InstantiationException e) {
		 Logger.getLogger("Error").error("", e);
	  } catch (final IllegalAccessException e) {
		 Logger.getLogger("Error").error("", e);
	  } catch (final IllegalArgumentException e) {
		 Logger.getLogger("Error").error("", e);
	  } catch (final InvocationTargetException e) {
		 Logger.getLogger("Error").error("", e);
	  }
	  return null;
   }

   /**
    * Loads the commands list and handlers information
    * 
    * @param cm
    *           Command mappings
    */
   private void loadCommand(final Command cm) {
	  handlers.put(cm.getName(), cm);
	  commandAliasMapping.put(cm.getName(), cm.getName());
	  for (final String s : cm.getAliases().split(",")) {
		 if (!"".equals(s)) {
			commandAliasMapping.put(s, cm.getName());
		 }
	  }
   }

   /**
    * Unloads all the instances of the command handlers and reloads the commands list
    */
   public void reload() {
	  handlers.clear();
	  instanciatedHandlers.clear();
	  commandAliasMapping.clear();
	  List<Command> commandList = Command.getCommands();

	  for (Command cmd : commandList) {
		 loadCommand(cmd);
	  }
   }
}
